package sicnu.cs.ich.common.common;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.ibatis.session.SqlSession;
import org.springframework.core.annotation.AnnotationUtils;
import sicnu.cs.ich.api.common.annotations.sql.SQLDBName;
import sicnu.cs.ich.api.common.annotations.sql.SQLIgnore;
import sicnu.cs.ich.common.util.FileUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 用于临时保存对象，同时转为SQL语句，后通过主动调用将所有对象存进数据库
 *
 * @author CaiKe
 * @date 2021/12/8 18:17
 */
@Slf4j
public class ObjectToSqlFileExecutor<T> {

    private final Class clazz;
    private String DB_NAME;
    /**
     * 用于执行SQL脚本
     */
    private final SqlSession sqlsession;
    /**
     * 文件保存路径的文件夹名称，以./为基本路径
     */
    private final String dirName;
    /**
     * 用于记录保存文件标志
     */
    private long logFileIndex = System.currentTimeMillis();
    /**
     * 文件最终路径
     */
    private String PATH;
    /**
     * 标识
     */
    private boolean first = false;
    /**
     * 写SQL脚本文件的流
     */
    private BufferedWriter bufferedWriter;

    private final Pattern COMPILE = Pattern.compile("[A-Z]");
    private final ReentrantLock LOCK = new ReentrantLock();
    private final Condition NOT_IMPORTING = LOCK.newCondition();
    private volatile boolean isImporting = false;

    public ObjectToSqlFileExecutor(Class<?> clazz, SqlSession sqlSession, String dirName) {
        this.clazz = clazz;
        this.sqlsession = sqlSession;
        this.dirName = dirName;
        this.init();
    }

    public void init() {
        PATH = createDir(dirName + File.separator).getAbsolutePath() + File.separator;
        createDir(dirName + File.separator + "error" + File.separator);
        // 获取类上注解标志的数据库，以及生成母版SQL文件
        try {
            this.generateBaseSQL(clazz);
        } catch (Exception e) {
            log.error("初始化实体失败, {}", e.getMessage());
            e.printStackTrace();
            System.exit(500);
        }
    }

    /**
     * 将对象输入到文件中
     *
     * @param t
     * @throws InterruptedException
     */
    public void tempStoreLog(T t) throws InterruptedException {
        LOCK.lockInterruptibly();
        try {
            // 正在执行导出操作，进行等待
            if (isImporting)
                NOT_IMPORTING.await();
            // TODO 第一个SQL不添加逗号
            //      后续调整为并发写入，（需要清除文件末尾的逗号）
            if (first) {
                synchronized (this) {
                    if (first) {
                        first = false;
                        bufferedWriter.write("\n" + toSQL(t));
                        return;
                    }
                }
            }
            bufferedWriter.write("\n," + toSQL(t));
        } catch (Exception e) {
            log.error("保存至文件出错：{}", t);
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
    }

    /**
     * 将之间暂存的对象SQL文件进行保存，同时调用SQL脚本执行
     *
     * @throws IOException
     */
    public void saveLogs() throws IOException {
        // 如果first仍为true，说明截止上次没有记录产生，直接返回
        if (first) return;
        String oldIndex = String.valueOf(logFileIndex);
        // 交换文件流时，保证没有线程正在写入旧日志文件
        isImporting = true;
        LOCK.lock();
        try {
            bufferedWriter.write(";");
            bufferedWriter.flush();
            bufferedWriter.close();
            // 更新文件后缀，更新文件流
            this.updateFileIndex();
            NOT_IMPORTING.signalAll();
        } finally {
            LOCK.unlock();
            isImporting = false;
        }
        try {
            this.executeSQL(sqlsession, oldIndex);
        } catch (SQLException throwables) {
            log.error("执行SQL出错：{}", oldIndex);
            throwables.printStackTrace();
        }
    }

    /**
     * 更新文件对象
     *
     * @throws IOException
     */
    public void updateFileIndex() throws IOException {
        first = true;
        logFileIndex = System.currentTimeMillis();
        // 拷贝母版SQL文件

        final File newSql = new File(PATH + logFileIndex + ".sql");
        FileUtils.copyFile(new File(PATH + "base.sql"), newSql);
        try {
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(PATH + logFileIndex + ".sql", true)));
        } catch (FileNotFoundException e) {
            log.error("获取文件流出错：{}", PATH + logFileIndex + ".sql");
            throw e;
        }
    }

    /**
     * 根据类生成基本的SQL母版文件（insert into(...) values）
     *
     * @param clazz
     * @throws IOException
     */
    public void generateBaseSQL(Class<?> clazz) throws IOException, NoSuchFieldException {
        final SQLDBName anno = Optional.ofNullable(AnnotationUtils.findAnnotation(clazz, SQLDBName.class))
                .orElseThrow(() -> new NoSuchFieldException("找不到目标注解"));
        final File baseSql = new File(PATH + "base.sql");
        try (FileWriter fileWriter = new FileWriter(baseSql, false)) {
            StringBuilder sql = new StringBuilder();
            sql.append("insert into `").append(anno.value()).append("`(");
            // sql.append("字段，大写换下划线");
            boolean first = true;
            for (Field field : clazz.getDeclaredFields()) {
                if (AnnotationUtils.findAnnotation(field, SQLIgnore.class) != null) {
                    continue;
                }
                // 第一个不添加，
                if (first) first = false;
                else sql.append(",");
                sql.append("`").append(humpToLine(field.getName())).append("`");
            }
            sql.append(") values ");
            fileWriter.write(String.valueOf(sql));
        }
        this.updateFileIndex();
    }

    /**
     * 驼峰转下划线
     */
    public String humpToLine(String str) {
        Matcher matcher = COMPILE.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    /**
     * 执行SQL脚本
     *
     * @param oldIndex
     * @throws SQLException
     */
    public void executeSQL(SqlSession sqlSession, String oldIndex) throws SQLException {
        // 存储数据库，mysql执行sql
        Connection conn = null;
        try (FileReader sql = new FileReader(PATH + oldIndex + ".sql")) {
            conn = sqlSession.getConfiguration().getEnvironment().getDataSource().getConnection();
            log.warn("conn:{}", conn);
            ScriptRunner runner = new ScriptRunner(conn);
            runner.setAutoCommit(false);
            runner.setStopOnError(true);
            runner.runScript(sql);
        } catch (IOException | SQLException e) {
            // 发生错误，进行回滚
            if (conn != null) {
                conn.rollback();
            }
            // 保存SQL文件
            log.error("执行Sql失败:{}，尝试备份", oldIndex);
            try {
                FileUtils.copyFile(new File(PATH + oldIndex + ".sql"), new File(PATH + File.separator + "error" + File.separator + oldIndex + ".sql"));
            } catch (IOException ioException) {
                log.error("备份文件出错");
            }
        } finally {
            // 提交事务commit
            if (conn != null) {
                conn.commit();
            }
        }
    }

    /**
     * 将对象转成SQL语句
     *
     * @param t
     * @return
     */
    public String toSQL(T t) {
        final Class<?> clazz = t.getClass();
        StringBuilder sql = new StringBuilder();
        sql.append("(");
        for (Field field : clazz.getDeclaredFields()) {
            if (AnnotationUtils.findAnnotation(field, SQLIgnore.class) != null) {
                continue;
            }
            sql.append("'");
            // 调用getXxx()
            final String name = field.getName();
            String methodName = "get" + firstToUpper(name);
            Object fieldValue = null;
            try {
                fieldValue = clazz.getDeclaredMethod(methodName).invoke(t);
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                log.error("invoke方法：{}失败", methodName);
                e.printStackTrace();
            }
            // 拼接SQL
            if (field.getType().isAssignableFrom(Date.class)) {
                sql.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) fieldValue)).append("', ");
            } else {
                sql.append(fieldValue).append("', ");
            }
        }
        sql.deleteCharAt(sql.length() - 2).append(")");
        return sql.toString();
    }

    /**
     * 将第一个字母转为大写
     *
     * @param name
     * @return
     */
    public String firstToUpper(String name) {
        if (name.charAt(0) >= 'a' && name.charAt(0) <= 'z') {
            return (char) (name.charAt(0) - 32) + name.substring(1);
        }
        return name;
    }

    public void close() throws IOException {
        this.bufferedWriter.close();
    }

    public File createDir(String parentDir) {
        String root = System.getProperty("user.dir");
        final File parent = new File(root, parentDir);
        if (!parent.exists()) {
            parent.mkdirs();
        }
        return parent;
    }

    /**
     * 删除second之前的SQL文件
     *
     * @param seconds
     */
    public void cleanOldSQLFiles(Long seconds) {
        final long limit = System.currentTimeMillis() - seconds * 1000L;
        for (File file : Objects.requireNonNull(new File(PATH).listFiles(File::isFile))) {
            final String name = file.getName();
            if (name.length() == 17) {
                if (Long.parseLong(name.substring(0, 13)) < limit) {
//                    log.info("删除文件：{}", file.getAbsolutePath());
                    file.deleteOnExit();
                }
            }
        }
    }

}